fix(tooling): raise the re-measure heap ceiling to 6144 and give the job the memory (A1) - #14805
Merged
Merged
Conversation
#14569) Reverted before this PR's final diff. `CI_TSC_HEAP_CEILING_MB` may only move on a reading taken where the verdict is taken -- the `Type Check · debt ledger` job on `ubuntu-latest` -- and this container cannot download job logs. Check-run ANNOTATIONS are readable over REST, so the probe emits its readings as `::notice` workflow commands from that job: - the runner's MemTotal/MemAvailable/Swap, image, nproc, and the gate process's own V8 `heap_size_limit` (the runner's default old space); - what else is resident at the point the re-measure starts (`ps` RSS census); - the `packages/qa/http-conformance` TEST_DEBT program -- the same generated project `measureTestDebt` writes -- run with `--extendedDiagnostics` under `--max-old-space-size=4096` and under `6144`, reporting tsc's own "Memory used", peak RSS, and the machine's minimum MemAvailable during each run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…NG_MB
The pin's provenance was archaeology through a failed job's GC trace, which
bracketed the runner's old space into [4040, 4148] MB. It is now a first-hand
reading, taken where the verdict is taken -- inside the `Type Check · debt
ledger` job, by a temporary probe step (reverted in the previous commit) that
emitted its numbers as `::notice` annotations:
runner ubuntu24 20260831.293.1, 4 vCPU, MemTotal 16,373,452 kB
(~15.6 GiB) -- not the 7 GB the finding assumed
consumers 153 processes / 940,316 kB (~918 MB); the job's steps are
sequential, so nothing runs beside the re-measure
this gate heap_size_limit 4144 MB with NODE_OPTIONS unset -- the runner's
V8 default, confirming the 4096 MB old space directly
heaviest qa/http-conformance's TEST_DEBT program under two caps:
program 4096 -> 4,077,718K used, 4,212,904 kB peak RSS, 26.84s check
6144 -> 4,420,706K used, 4,545,500 kB peak RSS, 21.90s check
The pair is the headroom reading the finding asked for: 343 MB more heap keeps
343 MB more live and finishes ~5s sooner, so under 4096 the program is paying
GC pressure to fit. The constant does NOT move on it, and the measurement is
why: the scarce resource is V8's default old space (4096 MB), not the runner's
memory, and this number describes that default exactly.
The comment also records what the measurement made mechanically visible --
raising the pin alone cannot deliver a roomier run. `remeasureHeapCeiling`
minimises over the pin and the running process's own limit, so a 6144 pin under
the runner's default still chooses 4144, and the `stale` arm then refuses the
run outright: `--re-measure` exits 1 before the first tsc. Reproduced against a
4144 MB process. Delivering a raise needs the gate PROCESS given the memory
first, which is a workflow decision and is escalated on #14569.
The self-test row for "a box shaped like CI" gains a note that its `+ 48` is
now the measured runner rather than a construction.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…c-heap-ceiling-6144
This was referenced Sep 3, 2026
…c-heap-ceiling-6144
…b the memory Ruling A1 (#14569, 2026-09-03). The 02:25Z ruling A — raise on a recorded measurement — is completed the only way the measurement allows: the pin and the process's actual old space move together. - .github/workflows/lint.yml: the `typecheck-debt` job's re-measure step now runs under `NODE_OPTIONS: --max-old-space-size=6144`, so the process running tsc really has the old space the pin describes. V8's default there is 4096 MB, measured on the runner. - CI_TSC_HEAP_CEILING_MB: 4096 -> 6144, with the runner measurement already written beside it kept as the evidence. - Two new `remeasureHeapCeiling` self-test rows pin both directions of the pairing: the runner as the workflow now starts it (6192 reported, caller cap tying the pin, chosen ceiling 6144 named as the CI pin), and the same runner WITHOUT the workflow line (its measured 4144 MB default, refused). The `stale` arm is untouched and still refuses any pin above the process's own limit — that refusal is what caught the bare constant raise, and it is what keeps the two halves inseparable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
baozhoutao
marked this pull request as ready for review
September 4, 2026 00:10
baozhoutao
enabled auto-merge
September 4, 2026 00:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #14569
Ruling A1 (comment
5528367074, 2026-09-03, director seat, decision batch 22, maintainer 「同意」) completes the earlier ruling A rather than replacing it: raise on a recorded measurement, delivered the only way that measurement allows. The measurement itself was taken in the previous round on this branch and is unchanged; what this round adds is the raise, in the two pieces that make it honest.The two halves — they ship together or not at all
.github/workflows/lint.ymltypecheck-debtjob's step "Re-measure the type-check DEBT / TEST_DEBT ledger" now runs underNODE_OPTIONS: --max-old-space-size=6144, so the process running tsc really has the old space the pin describesscripts/check-type-check-coverage.mjsCI_TSC_HEAP_CEILING_MB4096 to 6144, plus tworemeasureHeapCeilingself-test rows pinning both directions of the pairingThe runner measurement already written beside the constant stays as the evidence. The
stalearm is untouched — it still refuses any pin above the process's own limit, and that refusal is exactly what makes the pairing inseparable.Why a bare constant raise is not shippable, and how this PR proves it
remeasureHeapCeilingchooses the minimum of the pin and the limit the running process actually has, and refuses outright when a CI runner's own limit is below the pin. So with the constant at 6144 and noNODE_OPTIONS, the debt lane exits 1 before the first tsc — on every PR and onmain. Both directions were run on this tree, at the commit below, with the exit code captured before any pipe:A is the failure this card exists to avoid; B is the same tree with the workflow half in place. Both are now pinned as self-test rows so the pairing cannot rot silently:
heapLimitMb: 6192, caller cap 6144, on CI: chosen ceiling 6144, named as the CI pin (the tie-break keeps the pin's name, which is what the job log prints).heapLimitMb: 4144, on CI: chosen 4144,staleset.Is the runner measurement still valid?
Yes, and it is re-stated rather than re-taken. It was read first-hand inside the
Type Check · debt ledgerjob on 2026-09-03 (run 33708954003, job 100504131338, imageubuntu24 20260831.293.1, Node v22.23.2, 4 vCPU) and nothing in this round'smainmerge touches CI's runner image, its Node version, or that job's shape. The two readings the raise rests on:--max-old-space-size=4096--max-old-space-size=61446144 is therefore measured achievable on that runner, not assumed: it was run there. Box capacity is not the scarce resource either — MemTotal 16,373,452 kB with ~918 MB held by the job's other 153 processes, and the lowest MemAvailable seen at any moment of either run was 10,562,192 kB, about 1.7x the 6144 MB the pin now asks for.
One protection the pair costs, recorded rather than discovered later
With
NODE_OPTIONSset explicitly on that step,heap_size_limitthere reads 6192 whatever the runner's physical memory does. So on that one job thestalearm can no longer notice the runner shrinking — it now only notices a pin above a defaulted process. The margin above is what makes that acceptable, and the constant's comment says so in place, with the remedy if the margin is ever in doubt: a fresh runner measurement and a smaller number in both places, never a bigger one here.What is in the diff
Of the 115 changed lines in the gate script, 16 are non-comment (mechanically counted) and they are exactly: the constant, and the two self-test rows above. Everything else is the comment block that carries the measurement and the reason the two halves are inseparable. The 26 workflow lines are the
env:block plus the comment explaining why it cannot be edited alone.Verification — all of it at
a68aed7ec, the final commitnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no paths passed; it derives its own change set from the merge base) reports 45 families for this diff: 38 by path, 2 by change kind (this edits a gate script), 6 declared whole-tree, one family reached both ways. All 45 were harvested with--commandsand run, every exit code captured with redirect-then-capture, never through a pipe.check:type-check-coverage,check:pm-dispatch-gates,pm/bare-root-worklist --self-test,check-self-test-wired,check-self-test-workflow-commands,check-step-collectors,check-aggregator-roster,check-required-contexts,check-ci-filter-parity,check-closing-keyword-parity,check:nul-bytes,check:watch-hint-literal,check:shard-attestation,check:stall-guard-budget,check:stall-guard-headroom,check:workflow-status-functions,check:node-version,check:pnpm-acquisition.node scripts/check-test-completeness.mjsexits 3, its own declared "nothing was measured" code — no local vitest summary exists. Not a pass and not a finding.pnpm check:type-check-debtwas RUN, not narrowed — that is the family this diff actually moves, so last round's narrowing no longer applies. Full closure build first (turbo run build --filter='./packages/*' --filter='./packages/*/*', 71/71 tasks, 7m20s), then the gate, both through the shared verify lock:The re-measure case count moved 43 to 45 — the two new rows above.
The gate script's own suite, owed whether or not the derivation names it. This diff edits
scripts/check-type-check-coverage.mjs, a gate script. Its test surface is--self-test(green above, andcheck-self-test-wiredconfirms it is wired); agit grepfor the script's name across*.test.ts/*.test.mjsfinds 12 files, and every hit is a prose mention in a comment — none of them executes the script. There is no vitest pin suite to miss.ESLint — narrowing, declared, with its three readings.
pnpm lintiseslint . --no-inline-configover the whole repo and is CI's to run. Narrowed here to the changed files, and the narrowing is measured rather than assumed: (1) the population is read from ESLint's own config, not guessed —isPathIgnored('.github/workflows/lint.yml')is true, so the workflow half is not in ESLint's population at all, and the.mjsis false, so it is; (2) the file count comes from--format json— 1 file linted, 0 errors, 0 warnings; (3) config invariance for untouched files —calculateConfigForFilereportsparserOptions.projectundefined, i.e. type-aware linting is off, so nothing in this diff can move the verdict of a file it does not touch.Control bytes.
grep -naPover the control ranges across both edited files: exit 1, no matches, captured before any pipe.skip-changeset— already on this PR, and correct: nothing is published from any released package.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code